package top.codedance.iotp.common.util;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class Sm2Encrypt implements AEncryption {
    private static Logger logger = Logger.getLogger(Sm2Encrypt.class);
    private static final String ENCRYPT_TYPE = "sm2p256v1";
    private final static String KEY_RSA_SIGNATURE = GMObjectIdentifiers.sm2sign_with_sm3.toString();

    /**
     * 获取sm2密钥对
     * BC库使用的公钥=64个字节+1个字节（04标志位），BC库使用的私钥=32个字节
     * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示，
     * <br/>SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头，当启用压缩时:公钥=有头+公钥X ，即省略了公钥Y的部分
     * @return
     */
    @Override
    public AsymmetricEncryption.KeyStringPair genKeyPair() throws Exception {
        boolean compressed = false; //是否压缩公钥（加密解密都使用BC库才能使用压缩）
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(ENCRYPT_TYPE);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //1.创建密钥生成器
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        //2.初始化生成器,带上随机数
        try {
            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
        } catch (NoSuchAlgorithmException e) {
            logger.debug(e.getMessage());
        }
        //3.生成密钥对
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic();
        ECPoint ecPoint = publicKeyParameters.getQ();
        // 把公钥放入map中,默认压缩公钥
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
        String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed));
        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
        BigInteger intPrivateKey = privateKeyParameters.getD();
        // 把私钥放入map中
        String privateKey = intPrivateKey.toString(16);
        return new AsymmetricEncryption.KeyStringPair(privateKey, publicKey);
    }

    @Override
    public String encrypt(byte[] body, String publicKey) throws Exception{
        return encrypt(new String(body), publicKey);
    }

    /**
     * SM2加密算法
     * @param publicKey 公钥
     * @param body 待加密的数据
     * @return 密文，BC库产生的密文带由04标识符，与非BC库对接时需要去掉开头的04
     */
    @Override
    public String encrypt(String body, String publicKey) throws Exception{
        // 按国密排序标准加密
        return encrypt(body, publicKey, SM2EngineExtend.CIPHERMODE_NORM);
    }

    /**
     * SM2加密算法
     * @param body 待加密的数据
     * @param publicKey 公钥
     * @param cipherMode 密文排列方式0-C1C2C3；1-C1C3C2；
     * @return 密文，BC库产生的密文带由04标识符，与非BC库对接时需要去掉开头的04
     */
    public String encrypt(String body, String publicKey, int cipherMode) {
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(ENCRYPT_TYPE);
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // 设置sm2为加密模式
        sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
            byte[] in = body.getBytes();
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            logger.debug(e.getMessage());
        }
        return Hex.toHexString(arrayOfBytes);
    }

    @Override
    public String decrypt(byte[] body, String privateKey) throws Exception {
        return decrypt(new String(body), privateKey);
    }

    /**
     * SM2解密算法
     * @param body    密文数据
     * @param privateKey    私钥
     * @return
     */
    @Override
    public String decrypt(String body, String privateKey) throws Exception {
        // // 按国密排序标准解密
        return decrypt(body, privateKey, SM2EngineExtend.CIPHERMODE_NORM);
    }

    /**
     * SM2解密算法
     * @param body    密文数据
     * @param privateKey    私钥
     * @param cipherMode 密文排列方式0-C1C2C3；1-C1C3C2；
     * @return
     */
    public static String decrypt(String body, String privateKey, int cipherMode) {
        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (!body.startsWith("04")) {
            body = "04" + body;
        }
        byte[] cipherDataByte = Hex.decode(body);

        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(ENCRYPT_TYPE);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        SM2EngineExtend sm2Engine = new SM2EngineExtend();
        // 设置sm2为解密模式
        sm2Engine.init(false, cipherMode, privateKeyParameters);

        String result = "";
        try {
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            return new String(arrayOfBytes);
        } catch (Exception e) {
            logger.debug(e.getMessage());
        }
        return result;
    }

    @Override
    public String sign(String encryptedStr, String privateKey) {
        String str = "";
        try {
            byte[] data = encryptedStr.getBytes(StandardCharsets.UTF_8);
            byte[] bytes = Base64or32Tool.decode(privateKey.getBytes());
            // 构造PKCS8EncodedKeySpec对象
            PKCS8EncodedKeySpec pkcs = new PKCS8EncodedKeySpec(bytes);
            // 指定的加密算法
            KeyFactory factory = KeyFactory.getInstance(ENCRYPT_TYPE);
            // 取私钥对象
            PrivateKey key = factory.generatePrivate(pkcs);
            byte[] ret = sign(data, key);
            str = new String(ret);
        } catch (Exception e) {
            logger.debug(e.getMessage());
        }
        return str;
    }

    /**
     * 私钥签名
     *
     * @param data 未加密的密码明文
     * @param privateKey
     * @return
     * @throws Exception
     */
    public byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
        Signature sig = Signature.getInstance(KEY_RSA_SIGNATURE, BouncyCastleProvider.PROVIDER_NAME);
        sig.initSign(privateKey);
        sig.update(data);
        byte[] ret = sig.sign();
        return ret;
    }

    @Override
    public boolean verify(String body, String publicKey, String sign) {
        boolean flag = false;
        try {
            //将私钥加密数据字符串转换为字节数组
            byte[] data = body.getBytes(StandardCharsets.UTF_8);
            // 解密由base64编码的公钥
            byte[] bytes = Base64or32Tool.decode(publicKey.getBytes());
            // 构造X509EncodedKeySpec对象
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
            // 指定的加密算法
            KeyFactory factory = KeyFactory.getInstance(ENCRYPT_TYPE);
            // 取公钥对象
            PublicKey key = factory.generatePublic(keySpec);
            // 用公钥验证数字签名
            byte[] signature = sign.getBytes(StandardCharsets.UTF_8);
            flag = verify(data, key, signature);
        } catch (Exception e) {
            logger.debug(e.getMessage());
        }
        return flag;
    }

    /**
     * 公钥验签
     *
     * @param data 未加密的密码明文
     * @param publicKey
     * @param signature 签名
     * @return
     * @throws Exception
     */
    public boolean verify(byte[] data, PublicKey publicKey, byte[] signature) throws Exception {
        Signature sig = Signature.getInstance(ENCRYPT_TYPE, BouncyCastleProvider.PROVIDER_NAME);
        sig.initVerify(publicKey);
        sig.update(data);
        boolean ret = sig.verify(signature);
        return ret;
    }
}
